#ifndef BUFFER_H_
#define BUFFER_H_

#include <cassert>
#include <cstdint>
#include <string>
#include <vector>

#include "netlib/base/copyable.h"

namespace netlib::net {

class Buffer : Copyable {
	static const int kInitReadIndex = 8;
	static const int kInitSize = 1024;

public:
	constexpr static uint32_t kHeaderSizeSize = sizeof(int32_t);
	constexpr static uint32_t kCheckSumSize = sizeof(int32_t);
	explicit Buffer(int init_size = kInitSize) :
	    data_(init_size + kInitReadIndex), read_idx_(kInitReadIndex), write_idx_(kInitReadIndex) {
		assert(read_idx_ == write_idx_);
		assert(kInitSize == WritableBytes());
		assert(kInitReadIndex == PrependableBytes());
	}

	int ReadableBytes() const { return write_idx_ - read_idx_; }
	int WritableBytes() const { return static_cast<int>(data_.size() - write_idx_); }
	int PrependableBytes() const { return read_idx_; }

	int32_t PeekInt32() const { return *reinterpret_cast<const int32_t*>(&data_[read_idx_]); }
	// int32_t PeekInt32(uint32_t start) const {
	//   assert(read_idx_ + start + 4 <= data_.size());
	//   return *reinterpret_cast<const int32_t*>(&data_[read_idx_ + start]);
	// }
	const char* Peek() const { return &data_[read_idx_]; }
	std::string PeekString(int size) const {
		assert(size <= ReadableBytes());
		// return std::string(Peek(), size);
		return {Peek(), static_cast<std::size_t>(size)};
	}
	void Retrieve(int size);
	void RetrieveAll() { read_idx_ = write_idx_ = kInitReadIndex; }
	void RetrieveUntil(const char* end) { Retrieve(static_cast<int>(end - &data_[read_idx_])); }
	std::string RetrieveAsString(int size) {
		std::string res(Peek(), size);
		Retrieve(size);
		return res;
	}
	std::string RetrieveAllAsString() {
		std::string res(Peek(), ReadableBytes());
		RetrieveAll();
		return res;
	}
	const char* FindCrlf() const {
		for (int i = read_idx_; i < write_idx_; ++i) {
			if (data_[i] == '\r' && data_[i + 1] == '\n') {
				return &data_[i];
			}
		}
		return nullptr;
	}

	void Append(const void* data, int len);
	void Append(const char* data, int len);
	void Append(const std::string& str) { Append(str.c_str(), str.size()); }
	void Prepend(const char* data, int len);
	void Prepend(const void* data, int len) { Prepend(static_cast<const char*>(data), len); }
	int ReadFd(int fd, int* save_errno);

	// const char* begin() const { return &data_[read_idx_];
	// } const char* end() const { return }
	char* GetWriteBegin() { return &data_[write_idx_]; }
	void HasWritten(int size) { write_idx_ += size; }
	void AppendCheckSum();
	bool CheckCheckSum(uint32_t start, uint32_t end);
	void PrependSize();

private:
	bool IsEnoughSize(int len) const { return len <= WritableBytes(); }
	void ExpandCapacity(int len);

	std::vector<char> data_;
	int read_idx_;
	int write_idx_;
};

} // namespace netlib::net

#endif // BUFFER_H_